﻿using System;
using System.Collections.Generic;
using System.Text;
using OpenTK;
using System.Drawing;
//

using OpenTK.Graphics.OpenGL;
using System.IO;
using OpenTK;
namespace Xeno.Core
{
    using X = XenoFX;
    public class XenoFX
    {
        internal static FileStream _fs = null;
        internal static TextWriter _w = null;
        public static List<XenoEffectNode> NodeTypes = new List<XenoEffectNode>();
        public static void ConvertGraphToShader(XenoEffectNode m, string path)
        {
            _fs = new FileStream(path, FileMode.Create, FileAccess.Write);
            _w = new StreamWriter(_fs);
            WriteShader(m);
            WriteFile();
            _w.Flush();
            _fs.Flush();
            _fs.Close();
            _w = null;
            _fs = null;
        }
        internal static void WriteFile()
        {
            foreach (var c in PreCode)
            {
                _w.WriteLine(c);
            }
            foreach (var c in MainCode)
            {
                _w.WriteLine(c);
            }
            foreach (var c in PostCode)
            {
                _w.WriteLine(c);
            }
        }
        internal static void WriteShader(XenoEffectNode m)
        {
            MainCode.Clear();
            PreCode.Clear();
            PostCode.Clear();

            InitCode(@"
	uniform vec4 MaterialDiffuseColor;
	uniform vec4 MaterialSpecularColor;
	uniform float MaterialSpecularPower;
	uniform vec4 LightDiffuseColor;
	uniform vec4 LightAmbientColor;
	uniform vec3 LightPosition;
	uniform float LightRange;
	
	varying vec3 normal,lightDir,halfVector;
	varying vec3 position;
	
	uniform int DiffuseMapping;
	uniform int NormalMapping;
	uniform int MaterialLightType;		
	uniform int MaterialLightingBit;

	varying vec3 tangent,binormal;	
    

            
");
          
            WroteNodes.Clear();

            Func("main");

         
            
            Code("vec4 fDif;");
            ZeroVec4("fDif");


            WriteConnection(m.GetIn("Diffuse").To);

            Code("fDif = " + m.GetIn("Diffuse").To.Owner.PropNodeName + "_" + m.GetIn("Diffuse").To.ID + ";");



            Code("gl_FragColor = fDif;");
            Code("gl_FragColor.a =1.0;");

            Close();
        }
        internal static void ZeroVec4(string id){
            Code(id+".x=0.0;");
            Code(id+".y=0.0;");
            Code(id+".z=0.0;");
            Code(id+".w=0.0;");
        }
        public static List<string> MainCode = new List<string>();
        public static List<string> PreCode = new List<string>();
        public static List<string> PostCode = new List<string>();
        private static List<XenoEffectNode> WroteNodes = new List<XenoEffectNode>();
        internal static void WriteConnection(XenoEffectParameter to)
        {

            var n = to.Owner;

            

            if (WroteNodes.Contains(n))
            {

            }else
            {
                WroteNodes.Add(n);
                foreach (var bn in n.In)
                {
                    WriteConnection(bn.To);
                }
                
                n.ProduceSegment(n.In);
            }
        }

        internal static void Code(string code)
        {
            //_w.WriteLine(code);
            MainCode.Add(code);
        }
        public static void InitCode(string code)
        {
            PreCode.Add(code);
        }
        internal static void Func(string name)
        {
            Code("void " + name + "(){");
        }
        internal static void Close()
        {
            Code("}");
        }

        public static void RegisterNodeType(XenoEffectNode node)
        {
            NodeTypes.Add(node);
        }
        public static XenoEffectNode GetNodeType(string id)
        {
            foreach (var n in NodeTypes)
            {
                if (n.ID == id) return n;
            }
            return null;
        }
        public static void InitXenoFX()
        {
            RegisterNodeType(new xConstantFloat());
            RegisterNodeType(new xOperatorMultiply());
            RegisterNodeType(new xTexture2DNode());
            RegisterNodeType(new xReadTexture2DNode());
;
        }
    }

    public enum EffectParameterType
    {
        Float,Int,Texture1D,Texture2D,Texture3D,TextureCUBE,Vector2,Vector3,Vector4,Matrix,Quat,Constant,Condition,Anything
    }


    public class XenoEffectParameter
    {
        public string ID { get; set; }
        public string ThisName { get; set; }
        public EffectParameterType Type { get; set; }
        public float RawValue { get; set; }
        public Vector2 RawVec2 { get; set; }
        public Vector3 RawVec3 { get; set; }
        public Vector4 RawVec4 { get; set; }
        public XenoEffectNode Owner { get; set; }
        public string OwnerName { get; set; }
        public XenoEffectParameter To { get; set; }
        public string ToName { get; set; }
        public XenoEffectParameter()
        {
            ThisName = "";
        }
        public XenoEffectParameter(string id, EffectParameterType type)
        {
            ID = id;
            To = null;
            Type = type;
            RawValue = 0.0f;
            RawVec3 = Vector3.Zero;
            RawVec4 = Vector4.Zero;
            RawVec2 = Vector2.Zero;
            ToName = "";
            ThisName = "";
        }
        public void EstablishLinks()
        {
            if (ToName != "None")
            {
                To = XenoEffectNode.ParMap[ToName];
            }
                Owner = XenoEffectNode.NodeMap[OwnerName];
        }

    }
    public enum EffectNodeType
    {
        Constant,Function,Operation,EngineInput
    }
    public class XenoEffectNode
    {
        public static Dictionary<string, XenoEffectNode> NodeMap = new Dictionary<string, XenoEffectNode>();
        public static Dictionary<string, XenoEffectParameter> ParMap = new Dictionary<string, XenoEffectParameter>();
        public Vector2 UIPos { get; set; }
        public float UIWidth { get; set; }
        public float UIHeight { get; set; }
        public string ID { get; set; }
        public EffectNodeType NodeType{get;set;}
        
        public List<XenoEffectParameter> In = new List<XenoEffectParameter>();
        public List<XenoEffectParameter> Out = new List<XenoEffectParameter>();
        public string PropNodeName { get; set; }
        public string PropComment { get; set; }
        public void WriteParameters(BinaryWriter w)
        {
            foreach (var n in In)
            {
                WriteParameter(w, n);
            }
            foreach (var n in Out)
            {
                WriteParameter(w, n);
            }
        }
        private void WriteParameter(BinaryWriter w, XenoEffectParameter p)
        {
            w.Write(p.ID);
            w.Write(PropNodeName + "_" + p.ID);
            w.Write((int)p.Type);
            if (p.To != null)
            {
                w.Write((bool)true);
                w.Write(p.To.Owner.PropNodeName + "_" + p.To.ID);
            }
            else
            {
                w.Write((bool)false);
            }
                w.Write(p.Owner.PropNodeName);
        }
        public static XenoEffectParameter ReadParameter(BinaryReader r)
        {
            XenoEffectParameter p = new XenoEffectParameter();
            p.ID = r.ReadString();
            p.ThisName = r.ReadString();
            p.Type = (EffectParameterType)r.ReadInt32();
            if (r.ReadBoolean())
            {
                p.ToName = r.ReadString();
            }
            else
            {
                p.ToName = "None";
            }
            p.OwnerName = r.ReadString();
            return p;

        }
        public void WriteNode(BinaryWriter w)
        {
            w.Write((string)GetType().Name);
            w.Write(UIPos.X);
            w.Write(UIPos.Y);
            w.Write(UIWidth);
            w.Write(UIHeight);
            w.Write(ID);
            w.Write((int)NodeType);
            //w.Write(PropNodeName);
            //w.Write(PropComment);
            WriteProperties(w);
            w.Write(In.Count);
            foreach (var ep in In)
            {
                w.Write(PropNodeName+"_"+ep.ID);
            }
            w.Write(Out.Count);
            foreach (var ep in Out)
            {
                w.Write(PropNodeName + "_" + ep.ID);
            }
        }
        public void ReadProperties(BinaryReader w)
        {
              var t = GetType();
              foreach (var p in t.GetProperties())
              {
                  if (p.Name.Length < 5) continue;
                  if (p.Name.Substring(0, 4) == "Prop")
                  {
                      var tc = p.PropertyType;

                      if (tc == typeof(int))
                      {
                          p.SetValue(this, w.ReadInt32(), null);
                          //  w.Write((byte)0);
                          //w.Write(int.Parse(p.GetValue(this, null).ToString()));
                      }
                      if (tc == typeof(float))
                      {
                          p.SetValue(this, w.ReadSingle(), null);
                          //    w.Write((byte)1);
                      //    w.Write(float.Parse(p.GetValue(this, null).ToString()));
                      }
                      if (tc == typeof(string))
                      {

                          p.SetValue(this, w.ReadString(), null);
                          //w.Write(p.GetValue(this, null).ToString());
                      }
                      if (tc == typeof(bool))
                      {
                          p.SetValue(this, w.ReadBoolean(), null);
                          //w.Write((bool)p.GetValue(this, null));


                      }

                  }
              }
        }
        public void WriteProperties(BinaryWriter w)
        {
            var t = GetType();
            foreach (var p in t.GetProperties())
            {
                if (p.Name.Length < 5) continue;
                if (p.Name.Substring(0, 4) == "Prop")
                {
                    var tc = p.PropertyType;

                    if (tc == typeof(int))
                    {
                      //  w.Write((byte)0);
                        w.Write(int.Parse(p.GetValue(this,null).ToString()));
                    }
                    if (tc == typeof(float))
                    {
                    //    w.Write((byte)1);
                        w.Write(float.Parse(p.GetValue(this, null).ToString()));
                    }
                    if (tc == typeof(string))
                    {
                    
                        w.Write(p.GetValue(this, null).ToString());
                    }
                    if (tc == typeof(bool))
                    {
                       w.Write((bool)p.GetValue(this,null));
                    
                    }
                    
                }   
            }
        }
        public static XenoEffectNode ReadNode(BinaryReader w)
        {
            var nt = w.ReadString();
            var rn = Activator.CreateInstance(Type.GetType("Xeno.Core."+nt)) as XenoEffectNode;
            rn.In.Clear();
            rn.Out.Clear();
            rn.ReadNodeAux(w);
     
            return rn;
        }
        public void ReadNodeAux(BinaryReader w)
        {
            UIPos = new OpenTK.Vector2(w.ReadSingle(), w.ReadSingle());
            UIWidth = w.ReadSingle();
            UIHeight = w.ReadSingle();
            ID = w.ReadString();
            NodeType = (EffectNodeType)w.ReadInt32();
            ReadProperties(w);
            int ic = w.ReadInt32();
            for (int i = 0; i < ic; i++)
            {
                var s = w.ReadString();
                In.Add(ParMap[s]);
            }
            int oc = w.ReadInt32();
            for (int i = 0; i < oc; i++)
            {
                var s = w.ReadString();
                Out.Add(ParMap[s]);
            }
        }

        public XenoEffectNode()
        {
            UIPos = Vector2.Zero;
            UIWidth = UIHeight = 0;
            ID = "";
            NodeType = EffectNodeType.Constant;
            PropNodeName = "";
            PropComment = "";
        }
        public virtual XenoEffectNode CreateNew()
        {
            return new XenoEffectNode(ID, NodeType);
        }
        public string InTo(int i)
        {
            return In[i].To.Owner.PropNodeName + "_" + In[i].To.ID; 
        }
        public XenoEffectNode(string id,EffectNodeType type)
        {
            ID = id;
            NodeType=type;
            PropComment = "";
            PropNodeName = id;
        }
        public XenoEffectParameter GetIn(string id)
        {
            return _FindPar(id, In);
        }
        public XenoEffectParameter _FindPar(string id, List<XenoEffectParameter> pars)
        {
            foreach (var p in pars)
            {
                if (p.ID == id)
                {
                    return p;
                }
            }
            return null; 
        }
        public void AddIn(EffectParameterType type, string id, string info)
        {
            var ep = new XenoEffectParameter(id, type);
            In.Add(ep);
            ep.Owner = this;
        }
        public void AddOut(EffectParameterType type, string id, string info)
        {
            var ep = new XenoEffectParameter(id, type);
            Out.Add(ep);
            ep.Owner = this;
        }
        public virtual void ProduceSegment(List<XenoEffectParameter>pars)
        {
            return;
        }
        
    }
    public class xTexture2DNode : XenoEffectNode
    {

        public int PropUVSet { get; set; }

        public xTexture2DNode()
            : base("Texture2DNode", EffectNodeType.EngineInput)
        {
            this.AddOut(EffectParameterType.Texture2D, "RGBA", "Texture2D RGBA Value");
            PropUVSet = 0;
        }
        public override void ProduceSegment(List<XenoEffectParameter> pars)
        {

            if (X.PreCode.Contains("uniform sampler2D " + PropNodeName + ";"))
            {
                X.InitCode("//You have defined two texture nodes with the same name.:" + PropNodeName);
            }
            else
            {
                X.InitCode("uniform sampler2D " + PropNodeName + ";");
                //base.ProduceSegment(pars);
            }
        }
        public override XenoEffectNode CreateNew()
        {
            return new xTexture2DNode();    
        }
    }

    public class xReadTexture2DNode : XenoEffectNode
    {
        public xReadTexture2DNode()
            : base("xReadTexture2DNode", EffectNodeType.Function)
        {
            AddIn(EffectParameterType.Texture2D, "Texture2D", "Texture2D Input");
            AddOut(EffectParameterType.Vector4, "RGBA", "RGBA Color Value");
        }
        public override void ProduceSegment(List<XenoEffectParameter> pars)
        {

            X.Code("vec4 tex2d_" + In[0].To.Owner.PropNodeName +"_"+Out[0].ID+ " = texture2D( " + In[0].To.Owner.PropNodeName + " ,gl_TexCoord[0]);");
            PropNodeName = "tex2d_" + In[0].To.Owner.PropNodeName;

        
        }
        public override XenoEffectNode CreateNew()
        {
            return new xReadTexture2DNode();

        }
    }

    public class xConstantFloat : XenoEffectNode
    {
        
        public float PropFloatValue { get; set; }

        public xConstantFloat()
            : base("ConstantFloat", EffectNodeType.Constant)
        {
            PropFloatValue = 0.0f;
            AddOut(EffectParameterType.Float, "ConstantValue", "Constant Float Value");
        }
        public override XenoEffectNode CreateNew()
        {
            return new xConstantFloat();
        }
        public override void ProduceSegment(List<XenoEffectParameter> pars)
        {
            X.Code("const float " + PropNodeName + "_"+Out[0].ID+  " = " + PropFloatValue.ToString() + ";");

        }
    }
    public class xOperatorMultiply : XenoEffectNode
    {
        public xOperatorMultiply() : base("Multiply",EffectNodeType.Operation)
        {
            AddIn(EffectParameterType.Anything, "Value1", "First Value");
            AddIn(EffectParameterType.Anything, "Value2", "Second Value");
            AddOut(EffectParameterType.Anything, "OutputValue", "Result Value");
        }
        public override XenoEffectNode CreateNew()
        {
            return new xOperatorMultiply();
        }
        public override void ProduceSegment(List<XenoEffectParameter> pars)
        {
            var p1 = pars[0];
            var p2 = pars[1];
    //        switch (p1.To.Type)
  //          {

//                case EffectParameterType.Float:
                    PropNodeName = "floatResult";
                    if (X.MainCode.Contains("float floatResult_" + Out[0].ID + ";"))
                    {
                    }
                    else
                    {
                        X.Code("float floatResult_" + Out[0].ID + ";");
                    }
                    X.Code("floatResult_" + Out[0].ID + " = " + InTo(0) + " * " + InTo(1) + ";");
                    //X.Code("floatResult_"+Out[0].ID+" = " + InTo(0) + " * " + InTo(1) + ";");

                    Out[0].Type = EffectParameterType.Float;

                    return;
                //default:
               
               
       
            X.Code("//code-generation faliure here.Node:" + ID + " Type:" + this.NodeType.ToString());

        
        }
    }
    
    public class XenoEffectGraph
    {
        public XenoEffectNode ColorNode { get; set; }
        public XenoEffectNode NormalNode { get; set; }
        public XenoEffectNode SpecularNode { get; set; }
        public XenoEffectNode GlowNode { get; set; }
        public XenoEffectNode BlurNode { get; set; }
        public XenoEffectNode AmbientNode { get; set; }

        public XenoEffectGraph()
        {

        }
    }
    public class XenoEffect
    {
        private int _Geo;
        private int _Vertex;
        private int _Fragment;
        private int _Program;
        public bool HasGeo { get; private set; }
        public bool HasVertex { get; private set; }
        public bool HasFragment { get; private set; }
        public string VertexPath { get; set; }
        public string FragmentPath { get; set; }
        public XenoEffect(string vertPath, string fragPath)
        {
            VertexPath = vertPath;
            FragmentPath = fragPath;
            _Program = CreateProgram();
            if (vertPath != "")
            {
                _Vertex = CreateShader(vertPath, ShaderType.VertexShader);
                HasVertex = true;
            
            }
            if (fragPath != "")
            {
                _Fragment = CreateShader(fragPath, ShaderType.FragmentShader);
                HasFragment = true;
            }

                HasGeo = false;



         
            
            AttachShaders();
            GL.BindAttribLocation(_Program, 6, "Tangent");
            GL.BindAttribLocation(_Program, 7, "BiNormal");
            LinkEffect();
        }

        ~XenoEffect()
        {
            /*
            if (HasGeo)
            {
                GL.DetachShader(_Program, _Geo);
                GL.DeleteShader(_Geo);
            }

            if (HasVertex)
            {
                GL.DetachShader(_Program, _Vertex);
                GL.DeleteShader(_Vertex);
            }
            if (HasFragment)
            {
                GL.DetachShader(_Program, _Fragment);
                GL.DeleteShader(_Fragment);
            }
            GL.DeleteProgram(_Program);
            */
        }

        public void LinkEffect()
        {
            GL.LinkProgram(_Program);
        }
        public void AttachShaders()
        {
            
            if(HasGeo) GL.AttachShader(_Program, _Geo);
            if (HasVertex) GL.AttachShader(_Program, _Vertex);
            if (HasFragment) GL.AttachShader(_Program, _Fragment);

        }
        public void SetInt(string name, int val)
        {
            GL.Uniform1(GL.GetUniformLocation(_Program, name), val);
        }
        public void SetFloat(string name, float val)
        {
            GL.Uniform1(GL.GetUniformLocation(_Program, name), val);
        }
        public void SetFloats(String name, float[] vals)
        {
            GL.Uniform1(GL.GetUniformLocation(_Program, name), vals.Length, vals);
        }
        public void SetVec2(string name, Vector2 val)
        {
            GL.Uniform2(GL.GetUniformLocation(_Program, name), val);
        }
        public void SetVec2s(string name, Vector2[] vals)
        {
            float[] tmp = new float[vals.Length*2];
            int index = 0;
            foreach(var v in vals){

                tmp[index] = v.X;
                tmp[index+1] = v.Y;
                index+=2;
            }
            GL.Uniform1(GL.GetUniformLocation(_Program,name),tmp.Length,tmp);
           
            tmp = null;

        }
        public void SetVec3(string name, Vector3 val)
        {
            GL.Uniform3(GL.GetUniformLocation(_Program, name), val);
        }
        public void SetVec4(string name, Vector4 val)
        {
            GL.Uniform4(GL.GetUniformLocation(_Program, name), val);
        }
        public void SetMat4(string name, Matrix4 val,bool transpose)
        {
            GL.UniformMatrix4(GL.GetUniformLocation(_Program, name),transpose, ref val);
        }
        public void SetColor(string name, Color val)
        {
          
            SetVec4(name,new Vector4((float)val.R/255.0f,(float)val.G/255.0f,(float)val.B/255.0f,(float)val.A/255.0f));

        }
        public void Use()
        {
          
            GL.UseProgram(_Program);
      

        }
        public void Fin()
        {
            GL.UseProgram(0);
        }

        public static int CreateProgram()
        {
            int id = GL.CreateProgram();
            return id;
        }

        public static int CreateShader(string path, OpenTK.Graphics.OpenGL.ShaderType type)
        {
            int id = GL.CreateShader(type);

            FileStream fs = new FileStream(path,FileMode.Open,FileAccess.Read);
            TextReader tr = new StreamReader(fs);

            GL.ShaderSource(id, @tr.ReadToEnd());

            GL.CompileShader(id);

            int status;
            GL.GetShader(id, ShaderParameter.CompileStatus, out @status);

            if (status == (int)OpenTK.Graphics.OpenGL.All.False)
            {

                string err = string.Empty;

                GL.GetShaderInfoLog(id,out err);
                XenoDebug.LogError("Effect Compile Error:" + err + "\nShaderType:" + type.ToString(),"XenoEffect.CreateShader");

            }

            return id;
        }

    }
}
